﻿@page "/Websocket"
@using System.Diagnostics
@using System.IO
@using System.Net
@using System.Net.WebSockets
@using System.Threading
@using MissionPlanner
@using MissionPlanner.Comms
@using MissionPlanner.Utilities
@using MissionPlanner.Log
@using System.IO;
@using System.Runtime.CompilerServices

@using System.Text;
@using System.Security.Cryptography;
@using System.Runtime.InteropServices
@using Blazor.Extensions
@using Microsoft.AspNetCore.Components

@using Newtonsoft.Json
@using Org.BouncyCastle.Utilities.Encoders
@using Blazor.Extensions.Canvas
@using Blazor.Extensions.Canvas.Canvas2D
@using Blazor.Extensions.Canvas.WebGL
@using Microsoft.JSInterop.WebAssembly
@using MissionPlanner.ArduPilot
@inject Sotsera.Blazor.Toaster.IToaster Toaster

@inject Toolbelt.Blazor.SpeechSynthesis.SpeechSynthesis SpeechSynthesis

@using MissionPlanner.Controls
@using MissionPlanner.Mavlink
@implements IDisposable
@inject IFileReaderService fileReaderService;
@inject HttpClient Http
@using Sotsera.Blazor.Toaster
@using Tewr.Blazor.FileReader

<button @onclick="@(async()=> {  mode = modes.map; })">map</button>
<button @onclick="@(async()=> {  mode = modes.parmedit; })">param</button>
<select @onchange=@(async (item) => { var itemi = int.Parse(item.Value.ToString());
                                        var id = MAVList.FromID(itemi);
                                        mav.sysidcurrent = id.sysid;
                                        mav.compidcurrent = id.compid;
                    })>
    @if (mav != null)
    {
        @foreach (var mode in mav.MAVlist.GetRawIDS())
        {
            <option value=@(mode)>MAV@(MAVList.FromID(mode).sysid + " " + MAVList.FromID(mode).compid)</option>
        }
    }
</select>
<div style="width: 100%; height: 100%; display: @(mode == modes.map ? "block" : "none")">
    <div id="page" style="width: 100%; height: 100%;">
        <div style="height: 100%; display: flex; flex-direction: column; float: left; resize: horizontal; overflow: auto;">
            <div style="float: left;">
                <BECanvas Width="358" Height="268" @ref="_canvasReference"></BECanvas>
                <br>
                <canvas id="graphcanvas" height="80" style="width: 100%;"></canvas><br />
                <div id="curve_chart" style="width: 100%; height: 200px;"></div>
                <button @onclick=@(async() => { await mav.doARMAsync(mav.MAV.sysid, mav.MAV.compid, true);})>Arm</button>
                <button @onclick=@(async() => { await mav.doARMAsync(mav.MAV.sysid, mav.MAV.compid, false);})>Disarm</button>
                <select @onchange=@(async (item) => modeselected = item.Value.ToString())>
                    @foreach (var mode in Common.getModesList(mav?.MAV?.cs?.firmware ?? Firmwares.ArduCopter2))
                    {
                        <option value="@mode.Value">@mode.Value</option>
                    }
                </select>
                <button @onclick=@(async() => { mav.setMode(mav.MAV.sysid, mav.MAV.compid, modeselected);})>Set Mode</button><br />
                <button @onclick=@(async() => { await mav_mission.download(mav, mav.MAV.sysid, mav.MAV.compid, MAVLink.MAV_MISSION_TYPE.MISSION);})>Get Mission</button>
                <button @onclick=@(async() => { await mav_mission.download(mav, mav.MAV.sysid, mav.MAV.compid, MAVLink.MAV_MISSION_TYPE.FENCE);})>Get Fence</button>
                <button @onclick=@(async() => { await mav_mission.download(mav, mav.MAV.sysid, mav.MAV.compid, MAVLink.MAV_MISSION_TYPE.RALLY);})>Get Rally</button><br/>
                <button @onclick=@(async() => { mav.setMode("GUIDED"); await mav.doCommandAsync(mav.MAV.sysid, mav.MAV.compid, MAVLink.MAV_CMD.TAKEOFF, 0, 0, 0, 0, 0, 0, 2); })>TakeOff - 2m</button>
            </div>
            <div id="serverStatus" style="width: 100%; overflow-y: auto; flex-grow: 0;"></div>
        </div>
        <div style="height: 100%; display: flex; flex-direction: column; resize: horizontal; overflow: auto;">

            <div id="cesiumContainer" style="height: 100%; display: block;"></div>
            <div id="map" style="width: 100%; height: 100%;"></div>
        </div>
    </div>
    <div id="pageload" style="bottom: 0; right: 0; position: absolute; top: 0px; left: 0px; overflow: hidden; background-color: #80808080; @(pageloadvisible ? null : "display: none; ")">
        <div style="text-align: center;margin-top: 25%;">
            <div style="background-color: red;border-radius: 5px;box-shadow: 0 0 20px 7px black; display: inline-block;padding: 5px;background-color: #e6e6e6;">
                Raw MAVLink website url (wss:// for https)<br />
                <input type="text" id="wsuri" @bind="wsuri" style="width: 350px;" /><br />
                <button @onclick="wsconnect">Connect</button>
                <button @onclick="serialconnect">Serial Port</button>
            </div>
        </div>
    </div>
</div>

<div style="display: @(mode == modes.parmedit ? "block" : "none")">
    <button @onclick="getParamList">Get full param list</button>

    <table id="rawparams" class="display">
        <thead>
            <tr>
                <th>Name</th>
                <th>Value</th>
                <th>Options</th>
                <th>Desc</th>
                <th></th>
            </tr>
        </thead>
        @if (mav != null && mode == modes.parmedit)
        {
            <tbody>
                @foreach (var param in mav.MAV.param/*.OrderBy(a => a.Name)*/)
                {
                    <tr>
                        <td>@param.Name</td>
                        <td><input type="number" @onchange="@(async (newValue) => { changes[param.Name] = double.Parse(newValue.Value.ToString()); })" placeholder="@param.Value" value="@param.Value"></td>
                        <td>@ParameterMetaDataRepository.GetParameterOptionsInt(param.Name, mav.MAV.cs.firmware.ToString()).Select(a => a.Key + " = " + a.Value).Aggregate("", (current, next) => current + "; " + next)</td>
                        <td>@ParameterMetaDataRepository.GetParameterMetaData(param.Name, ParameterMetaDataConstants.DisplayName, mav.MAV.cs.firmware.ToString()) @ParameterMetaDataRepository.GetParameterMetaData(param.Name, ParameterMetaDataConstants.Description, mav.MAV.cs.firmware.ToString())</td>
                        <td><button @onclick="@(async ()=> paramchanged(param.Name))">Set</button></td>
                    </tr>
                }
            </tbody>
        }
    </table>  
</div>

@functions {
    Dictionary<string, double> changes = new Dictionary<string, double>();

    public enum modes
    {
        map,
        parmedit
    }

    protected HUD _hud = new HUD();

    public modes mode = modes.map;

    string modeselected = "";
    string currentmav = "";

    async void getParamList()
    {
        try
        {
            Toaster.Success("Getting param list");
            await mav.getParamListAsync(mav.MAV.sysid, mav.MAV.compid).ConfigureAwait(false);
            Toaster.Success("Retrieved param list");
            StateHasChanged();
            await JSRuntime.InvokeAsync<object>("evalline", new object[] { @"$('#rawparams').DataTable( { searching: true,  serverSide: false, retrieve: true    } );" }).ConfigureAwait(false);
        }
        catch
        {
            Toaster.Error(String.Format(Strings.ErrorReceivingParams));
        }
    }

    async void paramchanged(string name)
    {
        try
        {
            Toaster.Success("Setting " + name);
            var value = changes[name];
            await mav.setParamAsync(mav.MAV.sysid, mav.MAV.compid, name, value).ConfigureAwait(false);
            Toaster.Success(name + " set!");
            StateHasChanged();
        }
        catch
        {
            Toaster.Error(String.Format(Strings.ErrorSetValueFailed, name));
        }
    }


    int currentCount = 0;
    MAVLinkInterface mav;
    DateTime gpstime;
    static bool init = false;

    private Canvas2DContext _context;



    string wsuri { get; set; } = "ws://localhost:56781/websocket/raw";
    bool pageloadvisible = true;

    static Websocket Instance;

    private void Log(string message) => Console.WriteLine($"{DateTime.UtcNow.ToString("O")} - {message}");

    protected async override void OnInitialized()
    {
        Log("OnInitialized ");
        base.OnInitialized();

        init = false;

        Instance = this;

        Console.WriteLine("OnInitialized  Done");
    }

    protected override async Task OnInitializedAsync() => Log("OnInitializedAsync");

    //protected WebSocketHelper WebSocketHelper1;

    public async Task serialconnect()
    {
        pageloadvisible = false;
        running = false;
        StateHasChanged();

        await JSRuntime.InvokeAsync<object>("evalline", new object[] { @"
serial.requestPort()
    .then(port => { 
        port.connect().then(()=>{
        console.log('connected');
        window.serialport = port;
        port.onReceive = (data) => { DotNet.invokeMethodAsync('wasm', 'ProcessPacketStatic', '', data);}
        });     
    });
" }).ConfigureAwait(false);

        mavbasestream.ReadBufferUpdate += (sender, i) => { };
        mavbasestream.WriteCallback += async (sender, bytes) =>
        {
            if (bytes.Count() == 0)
                return;
            await JS.InvokeAsync<string>("window.serialport.send", bytes.ToArray());
        };


        JSRuntime.InvokeAsync<object>("startChart");

        StateHasChanged();
    }

    public async Task wsconnect()
    {

        ClientWebSocket webSocket = new ClientWebSocket();
        Console.WriteLine(wsuri);
        await webSocket.ConnectAsync(new Uri(wsuri), CancellationToken.None).ContinueWith(async (a) =>
    {
        pageloadvisible = false;
        running = false;
        ArraySegment<byte>
            bytesReceived = new ArraySegment<byte>
                (new byte[1024]);
        Toaster.Info(webSocket.State.ToString());
        while (webSocket.State == WebSocketState.Open)
        {
            //Console.WriteLine("webSocket.ReceiveAsync");
            await webSocket.ReceiveAsync(bytesReceived, CancellationToken.None);

            this.ProcessPacket("", bytesReceived.Array.ToArray());
        }
    });


        mavbasestream.ReadBufferUpdate += (sender, i) => { };
        mavbasestream.WriteCallback += async (sender, bytes) => {  await webSocket.SendAsync(new ArraySegment<byte>
            (bytes.ToArray()), WebSocketMessageType.Binary, true, CancellationToken.None); };


        //JSRuntime.InvokeAsync<object>("initWebSocket", wsuri);

        JSRuntime.InvokeAsync<object>("startChart");

        StateHasChanged();

    }
        /*
    private void WsOnError(string obj)
    {
    SpeechSynthesis.Speak(obj);
    Toaster.Error(obj);
    StateHasChanged();
    }

    private void WsOnMessage(BwsMessage obj)
    {
    ProcessPacket("", obj.MessageBinary);
    }

    private void WsOnStateChange(short obj)
    {
    if (WebSocketHelper1.bwsState == BwsState.Open)
    {
    pageloadvisible = false;
    }
    else
        {

    pageloadvisible = true;
    }
    SpeechSynthesis.Speak(WebSocketHelper1.bwsState.ToString());
    Toaster.Info(WebSocketHelper1.bwsState.ToString());
    StateHasChanged();
    }
    */

    internal byte[] buffer = new byte[1024];

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (init)
            return;

        Log("OnAfterRenderAsync");

        init = true;

        var js = JSRuntime as WebAssemblyJSRuntime;
        js.InvokeUnmarshalled<byte[], bool>
            ("setNetBuffer1024", buffer);

        var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

        JSRuntime.InvokeAsync<object>
            ("initMap", null);

        JSRuntime.InvokeAsync<object>
            ("draw", null);

        {
            _hud.Context = await this._canvasReference.CreateCanvas2DAsync();
            _hud.Width = (int)_canvasReference.Width;
            _hud.Height = (int)_canvasReference.Height;
        }
    }

    protected BECanvasComponent _canvasReference;

    protected override void OnParametersSet()
    {
        Log("OnParametersSet");



    }

    protected override async Task OnParametersSetAsync()
    {
        Log("OnParametersSetAsync");


    }

    [JSInvokable]
    public static async void ProcessPacketStatic(string json, byte[] data = null)
    {
        Instance.ProcessPacket(json, data);
    }


    public async void ProcessPacket(string json, byte[] data = null)
    {
        //Console.WriteLine(DateTime.Now + " JS to C# " + json.ToArray().Length);

        DateTime start = DateTime.Now;


        if (!init)
        {
            Console.WriteLine("need to init first");
            return;
        }

        if (this == null)
        {
            Console.WriteLine("this is null");
            return;
        }

        //StateHasChanged();
        //Console.WriteLine("mav null?");
        if (mav == null)
        {
            Console.WriteLine("set mav");
            mav = new MAVLinkInterface();

            // just to prevent null exceptions
            mav.BaseStream = mavbasestream;


            mav.SubscribeToPacketType(MAVLink.MAVLINK_MSG_ID.GLOBAL_POSITION_INT, packetReceived,1,1);
            mav.SubscribeToPacketType(MAVLink.MAVLINK_MSG_ID.GPS_RAW_INT, packetReceived,1,1);
            mav.SubscribeToPacketType(MAVLink.MAVLINK_MSG_ID.HEARTBEAT, packetReceived,1,1);
            //mav.SubscribeToPacketType(MAVLink.MAVLINK_MSG_ID.PARAM_VALUE, packetReceived);

            mav.SubscribeToPacketType(MAVLink.MAVLINK_MSG_ID.STATUSTEXT, packetReceived,1,1);
        }

        //Console.WriteLine("decode json");
        //Console.WriteLine("decode json");

        if (json != "")
            data = Base64.Decode(json);

        mavbasestream.AppendBuffer(data);

                                                    /*
                                                    var pos = mav.logplaybackfile.BaseStream.Position;

                                                    mav.logplaybackfile.BaseStream.Seek(0, SeekOrigin.End);

                                                    byte[] datearray =
                                                    BitConverter.GetBytes(
                                                    (UInt64)((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds * 1000));
                                                    Array.Reverse(datearray);
                                                    mav.logplaybackfile.BaseStream.Write(datearray, 0, datearray.Length);

                                                    mav.logplaybackfile.BaseStream.Write(data, 0, data.Length);

                                                    mav.logplaybackfile.BaseStream.Seek(pos, SeekOrigin.Begin);

                                                    mav.logreadmode = true;
        */
        //Console.WriteLine("open {0} btr {1} type {2} type {3}", mav.BaseStream.IsOpen, mav.BaseStream.BytesToRead, mav.BaseStream.GetType(), mav.BaseStream.BaseStream?.GetType());

        var check1 = DateTime.Now;

        await ProcessPacketsint();

        //var packet = await

        //((MemoryStream)mav.logplaybackfile.BaseStream).SetLength(0);

        //Console.WriteLine("convert packet to bytes {0} everything {1}",check1-start, DateTime.Now - start);
        //mav.DebugPacket(packet, true);
    }


    bool running = false;

    public async Task ProcessPacketsint()
    {
        //Console.WriteLine($"ProcessPacketsint  {mav?.BaseStream?.BytesToRead} run {running} init {init} mav {mav}");

        if (running)
            return;

        if (!init)
            return;

        if (this == null)
        {
            Console.WriteLine("this is null");
            return;
        }

        if
            (mav == null || mav.BaseStream == null)
        {
            Console.WriteLine("ProcessPackets mav is null");
            return;
        }

        if (mav.BaseStream.BytesToRead < 200)
            return;


        //Console.WriteLine("ProcessPacketsint btr:" + mav.BaseStream.BytesToRead);

        //var th = new Thread(() =>
        {
            //while (mav != null)
            {

                //running = true;
                DateTime end = DateTime.Now.AddMilliseconds(30);
                while (DateTime.Now < end && mav.BaseStream.BytesToRead > 15 && !mav.giveComport)
                {
                    await mav.readPacketAsync().ConfigureAwait(false);
                }

                if (mav.BaseStream.BytesToRead > 20000)
                {
                    Console.WriteLine("Clearing data as behind " + mav.BaseStream.BytesToRead);
                    mav.BaseStream.DiscardInBuffer();
                }
                //Thread.Sleep(20);
                //Task.Delay(20);
            }
        } //);
    }


    DateTime nextUpdate = DateTime.Now.AddMilliseconds(100);
    private CommsInjection mavbasestream  = new CommsInjection();

    private bool packetReceived(MAVLink.MAVLinkMessage packet)
    {
        if (packet.data is MAVLink.mavlink_global_position_int_t)
        {
            var pos = (MAVLink.mavlink_global_position_int_t)packet.data;
            //Console.WriteLine("Update mavlink_global_position_int_t ##############################");
        }
        else if (packet.data is MAVLink.mavlink_gps_raw_int_t)
        {
            var pos = (MAVLink.mavlink_gps_raw_int_t)packet.data;
            //StateHasChanged();
            //Console.WriteLine("Update mavlink_gps_raw_int_t ##############################");
        }
        else if (packet.data is MAVLink.mavlink_heartbeat_t)
        {
            var hb = (MAVLink.mavlink_heartbeat_t)packet.data;

            //mav.DebugPacket(packet, true);
        }
        else if (packet.data is MAVLink.mavlink_param_value_t)
        {
            var param = (MAVLink.mavlink_param_value_t)packet.data;

            return true;

            //Console.WriteLine("{0} {1}", ASCIIEncoding.ASCII.GetString(param.param_id), param.param_value);
        }
        else if (packet.msgid == (int) MAVLink.MAVLINK_MSG_ID.STATUSTEXT)
        {
            var txt = (MAVLink.mavlink_statustext_t) packet.data;
            if (txt.severity >= (byte) MAVLink.MAV_SEVERITY.NOTICE)
            {
                Toaster.Info(Encoding.ASCII.GetString(txt.text).TrimUnPrintable());
            }
            else if (txt.severity >= (byte) MAVLink.MAV_SEVERITY.WARNING)
            {
                Toaster.Warning(Encoding.ASCII.GetString(txt.text).TrimUnPrintable());
                SpeechSynthesis.Speak(Encoding.ASCII.GetString(txt.text).TrimUnPrintable());
            }
            else if (txt.severity >= (byte) MAVLink.MAV_SEVERITY.EMERGENCY)
            {
                Toaster.Error(Encoding.ASCII.GetString(txt.text).TrimUnPrintable());
                SpeechSynthesis.Speak(Encoding.ASCII.GetString(txt.text).TrimUnPrintable());
            }
        }

        // update gui if neede
        if (DateTime.Now > nextUpdate)
        {
            nextUpdate = DateTime.Now.AddMilliseconds(333);

            foreach (var mavd in mav.MAVlist)
            {
                mavd.cs.UpdateCurrentSettings(null, false, mav, mavd);
                JSRuntime.InvokeAsync<object>
                    ("setPosition", new object[] { mavd.sysid, mavd.compid, mavd.cs.lat, mavd.cs.lng, mavd.cs.altasl, mavd.cs.roll, mavd.cs.pitch, mavd.cs.yaw });
                if (mavd.sysid == mav.sysidcurrent && mavd.compid == mav.compidcurrent)
                    JSRuntime.InvokeAsync<object>
                        ("setAttitude", new object[] { mavd.sysid, mavd.compid, mavd.cs.roll, mavd.cs.pitch, mavd.cs.yaw });

                if(DateTime.Now.Second % 5 == 0)
                    JSRuntime.InvokeAsync<object>("setMission", new object[] { mavd.sysid, mavd.compid, mavd.wps.Values.Where(a=>a.x != 0 && a.y != 0).ToJSON(), mavd.fencepoints.Values.ToJSON(), mavd.rallypoints.Values.ToJSON() });

                if (mavd.sysid == mav.sysidcurrent && mavd.compid == mav.compidcurrent)
                {
                    var hud1 = _hud;
                    hud1.airspeed = mavd.cs.airspeed;
                    hud1.alt = mavd.cs.alt;
                    hud1.batterylevel = (float) mavd.cs.battery_voltage;
                    hud1.batteryremaining = mavd.cs.battery_remaining;
                    hud1.connected = mavd.cs.connected;
                    hud1.current = (float) mavd.cs.current;
                    hud1.datetime = mavd.cs.datetime;
                    hud1.disttowp = mavd.cs.wp_dist;
                    hud1.ekfstatus = mavd.cs.ekfstatus;
                    hud1.failsafe = mavd.cs.failsafe;
                    hud1.gpsfix = mavd.cs.gpsstatus;
                    hud1.gpsfix2 = mavd.cs.gpsstatus2;
                    hud1.gpshdop = mavd.cs.gpshdop;
                    hud1.gpshdop2 = mavd.cs.gpshdop2;
                    hud1.groundalt = (float) mavd.cs.HomeAlt;
                    hud1.groundcourse = mavd.cs.groundcourse;
                    hud1.groundspeed = mavd.cs.groundspeed;
                    hud1.heading = mavd.cs.yaw;
                    hud1.linkqualitygcs = mavd.cs.linkqualitygcs;
                    hud1.message = mavd.cs.messageHigh;
                    hud1.mode = mavd.cs.mode;
                    hud1.navpitch = mavd.cs.nav_pitch;
                    hud1.navroll = mavd.cs.nav_roll;
                    hud1.pitch = mavd.cs.pitch;
                    hud1.roll = mavd.cs.roll;
                    hud1.status = mavd.cs.armed;
                    hud1.targetalt = mavd.cs.targetalt;
                    hud1.targetheading = mavd.cs.nav_bearing;
                    hud1.targetspeed = mavd.cs.targetairspeed;
                    hud1.turnrate = mavd.cs.turnrate;
                    hud1.verticalspeed = mavd.cs.verticalspeed;
                    hud1.vibex = mavd.cs.vibex;
                    hud1.vibey = mavd.cs.vibey;
                    hud1.vibez = mavd.cs.vibez;
                    hud1.wpno = (int) mavd.cs.wpno;
                    hud1.xtrack_error = mavd.cs.xtrack_error;
                    hud1.AOA = mavd.cs.AOA;
                    hud1.SSA = mavd.cs.SSA;
                    hud1.critAOA = mavd.cs.crit_AOA;

                    if (hud1.invalidated)
                        _hud.Refresh();
                }
            }

            gpstime = DateTime.Now;

            if (mode == modes.map)
            {
                //StateHasChanged();
            }
        }

        // once a second
        if (second != DateTime.Now.Second)
        {
            if (mode == modes.map)
            {
                second = DateTime.Now.Second;
                StateHasChanged();
            }
        }

        return true;
        //mav.DebugPacket(packet, true);
    }

    int second = 0;

    public string getCS()
    {
        var cs = JsonConvert.SerializeObject(mav.MAV.cs);
        //var wps = JsonConvert.SerializeObject(MainV2.comPort.MAV.wps);

        return cs;
    }

    public void Dispose()
    {
        mav?.Dispose();

        //WebSocketHelper1?.Close();
    }

}